/*
* @Descripttion:滑动节点复用scroll组件-Grid
* @Author:jindekun
* @Date: 2021-11-01 11:09:03
*/

import * as cc from "cc";
import NodeUtils from "../../../utils/NodeUtils";
import { ViewCtrlBase } from './../../../view/ViewCtrlBase';
import ListViewItemBase from './ListViewItemBase';

export interface GridListViewParams {
    /**数据源 */
    arr_data: any[],
    /**控制器 */
    vc: ViewCtrlBase,
    /**是否清空重新创建 */
    is_clean?: boolean,
    /**创建间隔 */
    interval?: number,
    /**创建节点的起始索引 */
    from_index?: number,

    /**初始化节点的方法 */
    updateItem?:(node:cc.Node,data:any,idx:number) => void,
}

const { ccclass, property, menu } = cc._decorator;
@ccclass("GridListView")
@menu("扩展组件/GridListView")
export default class GridListView extends cc.Component {
    // use this for initialization

    /**列表选项 */
    @property({ type: cc.Prefab, tooltip: "列表选项预制体", displayName: "列表选项预制体" })
    itemPrefab: cc.Prefab = null;

    /**列表选项类组件类 */
    @property({ tooltip: "列表选项类组件类,必须继承自ListViewItemBase", displayName: "列表选项类组件类名" })
    itemClassName: string = "";

    /**列表滚动容器 */
    @property({ type: cc.ScrollView, tooltip: "列表滚动容器" })
    scrollView: cc.ScrollView = null;

    /**列表项之间间隔 */
    @property({ tooltip: "列表项之间间隔", displayName: "列表项间隔" })
    spacing: cc.Vec2 = cc.v2(0, 0);

    /**item的高度 */
    @property({ tooltip: "列表选项尺寸,必须要有", displayName: "列表选项尺寸" })
    itemSize: cc.Size = cc.size(100, 100);

    /**初始化item额外创建数量 */
    @property({ tooltip: "初始化item额外创建数量,防穿帮", displayName: "额外创建数量" })
    spawnCountAdd: number = 0;

    @property({ tooltip: "滑动容器顶部预留距离", displayName: "顶部预留距离" })
    topPadding: number = 0;
    @property({ tooltip: "滑动容器底部预留距离", displayName: "底部预留距离" })
    bottomPadding: number = 0;
    @property({ tooltip: "滑动容器左部预留距离", displayName: "左边预留距离" })
    leftPadding: number = 0;
    @property({ tooltip: "滑动容器右部预留距离", displayName: "右边预留距离" })
    rightPadding: number = 0;

    /**是否使用每帧更新 */
    @property({ tooltip: "是否使用每帧更新,如果不用则监听scrolling", displayName: "是否使用update刷新" })
    useUpdate: boolean = false;
    /**刷新间隔时间/秒 */
    @property({ tooltip: "刷新间隔时间/秒", displayName: "刷新间隔时间/秒", visible() { return this.useUpdate } })
    updateFrameTime: number = 0.01

    /**列表项实例数量 */
    private _spawnCount: number = 0;
    /**距离scrollView中心点的距离，超过这个距离的item会被重置，一般设置为 scrollVIew.height/2 + item.heigt/2 + spaceing，因为这个距离item正好超出scrollView显示范围 */
    private _bufferZone: number = 0;
    /**列表项总数 */
    public _totalCount: number = 0;
    /**scrollView的内容容器 */
    private _content: cc.Node = null;
    /**存放列表项实例的数组 */
    private _items: Array<cc.Node> = [];
    /**上一次content的坐标值，用于和现在content的坐标值比较，得出滑动方向 */
    private _lastContentPos: cc.Vec3 = cc.Vec3.ZERO;
    /**列表项数据 */
    private _itemDataList: any[] = [];

    /**每行/列 实例数量 */
    private _spawnItemCount: number = 0;

    private _scrollViewUITrans: cc.UITransform = null;
    private _contentUITrans: cc.UITransform = null;
    private _timer: number = 0;
    private vc: ViewCtrlBase;

    private updateItem:(node:cc.Node,data:any,idx:number) => void = null;
    private readonly TAG = "ListView";
    onLoad() {
        //初始化
        this._scrollViewUITrans = this.scrollView.node.getComponent(cc.UITransform);
        this._content = this.scrollView.content;
        this._contentUITrans = this._content.getComponent(cc.UITransform);
        this._items = [];


        this.scrollView.vertical = true;
        this.scrollView.horizontal = false;
        this._contentUITrans.anchorX = 0.5;
        this._contentUITrans.anchorY = 1;
        this._content.parent.getComponent(cc.UITransform).anchorPoint = this._contentUITrans.anchorPoint.clone();
        this._content.removeAllChildren();
        this._content.setPosition(0, 0);

        this._spawnItemCount = Math.floor(this._scrollViewUITrans.width / (this.itemSize.width + this.spacing.x));
        //计算创建的item实例数量，比当前scrollView容器能放下的item数量再加上_spawnCountAdd个
        this._spawnCount = (Math.ceil(this._scrollViewUITrans.height / (this.itemSize.height + this.spacing.y)) + this.spawnCountAdd) * this._spawnItemCount;
        //计算bufferZone
        this._bufferZone = this._scrollViewUITrans.height / 2 + ((this.spawnCountAdd + 1) * (this.itemSize.height + this.spacing.y)) / 2;
        //暂停滚动
        this.enabled = false;
        this.scrollView.enabled = false;
        if (!this.useUpdate) {
            this.scrollView.node.on("scrolling", this.onScrolling, this);
        }
        this._lastContentPos = this.scrollView.content.position.clone();
    }

    /**
     * 设置item的数据
     * @example
     *   setData([{id:1,msg:"a"},{id:2,msg:"b"}])
     * @param itemDataList item数据列表
     * @param vc 控制器
     * @param isClean 是否清除原本,重新创建
     * @param interval 异步创建间隔/秒
     */
    public async reloadData(params: GridListViewParams) {
        this.vc = params.vc;
        this.updateItem = params.updateItem;
        if (params.arr_data.length != this._totalCount) {
            params.is_clean = true;
        }
        //复制item数据，如果item数据源改变，则需要重新setData一次来显示新数据
        this._itemDataList = params.arr_data.slice();
        this._totalCount = this._itemDataList.length;//暂停滚动
        this.enabled = false;
        this.scrollView.enabled = false;
        await this.createItem(params);
        if (!this.node || !this.node.isValid) {
            return;
        }
        //运行滚动
        this.enabled = true;
        this.scrollView.enabled = true;
    }

    /**创建item实例 */
    private async createItem(params: GridListViewParams) {
        this._contentUITrans.height = Math.ceil(this._totalCount / this._spawnItemCount) * (this.itemSize.height + this.spacing.y) + this.spacing.y + this.topPadding + this.bottomPadding;
        this._contentUITrans.width = this._spawnItemCount * (this.itemSize.width + this.spacing.x) + this.spacing.x + this.leftPadding + this.rightPadding;

        if (params.is_clean != false) {
            this._content.setPosition(0, 0);
            this.clearAllItem();

            let len = this._totalCount < this._spawnCount ? this._totalCount : this._spawnCount;
            let startIdx = 0;

            if (params.from_index != undefined) {
                //从指定索引开始创建
                let fromRow = Math.floor(params.from_index / this._spawnItemCount);
                let fromIdx = this._spawnItemCount * fromRow
                startIdx = Math.max(Math.min(this._totalCount - len, (fromIdx || 0)), 0);
                let row = Math.floor(fromIdx / this._spawnItemCount);
                let totalRow = Math.ceil(this._totalCount / this._spawnItemCount);
                let fromPosIdx = fromIdx;
                let maxFromRow = totalRow - this._spawnCount / this._spawnItemCount + 1 + this.spawnCountAdd;
                if (maxFromRow < row || 0) {
                    fromPosIdx = maxFromRow * this._spawnItemCount;
                }
                let contentPos = this._getContentPosByItemId(fromPosIdx)
                this._content.setPosition(contentPos)
            }

            let arrDataTemp = [];
            for (let i = 0; i < len; i++) { // spawn items, we only need to do this once
                const data = this._itemDataList[i + startIdx]
                if (!!data) {
                    arrDataTemp[i] = data;
                }
            }

            return NodeUtils.genNodeWithPrefabAndData(arrDataTemp, this.itemPrefab, params.interval || 0, (item: cc.Node, data: any, idx: number) => {
                const itemIdx = idx + startIdx;
                const posInContent = this._getItemPosByItemId(itemIdx);
                item.setPosition(posInContent);
                if (this.itemClassName) {
                    const com: ListViewItemBase = (item.getComponent(this.itemClassName) || item.addComponent(this.itemClassName)) as ListViewItemBase;
                    com.setDelegage(this.vc)
                    com.updateItem(itemIdx, data, this._itemDataList.length);
                }
                params.updateItem && params.updateItem(item, data, itemIdx);
                item.setParent(this._content);
                this._items.push(item);
            }, this)
        } else {
            for (let i = 0; i < this._items.length; i++) {
                const item = this._items[i];
                if (this.itemClassName) {
                    const com: ListViewItemBase = (item.getComponent(this.itemClassName) || item.addComponent(this.itemClassName)) as ListViewItemBase;
                    const data = this._itemDataList[com.itemIdx]
                    com.setDelegage(this.vc)
                    com.updateItem(com.itemIdx, data, this._itemDataList.length);
                    params.updateItem && params.updateItem(item, data, com.itemIdx);
                }
            }
        }
    }

    /**清理item实例 */
    private clearAllItem() {
        for (let i = 0, len = this._items.length; i < len; i++) {
            let item = this._items[i];
            item.destroy();
        }
        this._items.length = 0;
    }

    /**获取item在scrollView的局部坐标 */
    private getPositionInView(item: cc.Node) {
        let worldPos = this._contentUITrans.convertToWorldSpaceAR(item.position);
        let viewPos = this._scrollViewUITrans.convertToNodeSpaceAR(worldPos);
        return viewPos;
    }

    onScrolling() {
        if (this.scrollView.content.position.x === this._lastContentPos.x && this.scrollView.content.position.y === this._lastContentPos.y) {
            return;
        }
        let items = this._items;
        let buffer = this._bufferZone;
        let dir = cc.v2(0, 0);
        let offset = 0;
        dir.y = this.scrollView.content.position.y > this._lastContentPos.y ? 1 : -1;
        offset = (this.itemSize.height + this.spacing.y) * Math.floor(items.length / this._spawnItemCount);

        for (let i = 0; i < items.length; ++i) {
            let nodeItem = items[i];
            let viewPos = this.getPositionInView(nodeItem);
            if (this.scrollView.vertical) {
                if (dir.y == 1 && viewPos.y <= buffer) {
                    continue;
                }
                if (dir.y == -1 && viewPos.y >= -buffer) {
                    continue;
                }
            }
            if (this.itemClassName) {
                let comItem: ListViewItemBase = nodeItem.getComponent(this.itemClassName) as ListViewItemBase;
                let itemIdx = -1
                itemIdx = comItem.itemIdx + items.length * dir.y;
                if (this._itemDataList[itemIdx]) {
                    nodeItem.setPosition(cc.v3(nodeItem.position.x - offset * dir.x, nodeItem.position.y - offset * dir.y, nodeItem.position.z));
                    comItem.updateItem(itemIdx, this._itemDataList[itemIdx], this._itemDataList.length);
                    this.updateItem && this.updateItem(nodeItem, this._itemDataList[itemIdx], itemIdx);
                }
            }
        }
        // update lastContentPos
        this._lastContentPos = this.scrollView.content.position.clone();
    }


    update(dt) {
        if (!this.useUpdate) {
            return;
        }
        this._timer += dt;
        if (this._timer >= this.updateFrameTime) {
            this._timer = 0;
            this.onScrolling();
        }
    }

    private _getContentPosByItemId(itemIdx: number) {
        let row = Math.floor(itemIdx / this._spawnItemCount);
        let offsetY = Math.max(Math.min((this.itemSize.height + this.spacing.y) * row, this._contentUITrans.height - this._scrollViewUITrans.height), 0);
        return cc.v3(0, offsetY - this.topPadding);
    }

    /**
     * @desc 根据item索引计算item坐标
     * @param itemIdx item索引
     * @returns 
     */
    private _getItemPosByItemId(itemIdx: number) {
        let row = Math.floor(itemIdx / this._spawnItemCount);
        let col = itemIdx % this._spawnItemCount;
        let rowWidth = this._spawnItemCount * (this.itemSize.width + this.spacing.x) + this.spacing.x
        let x = -this._contentUITrans.width / 2 + this.leftPadding + this.itemSize.width * (0.5 + col) + this.spacing.x * (col + 1);
        let y = -this.topPadding - this.itemSize.height * (0.5 + row) - this.spacing.y * (row + 1);
        return cc.v3(x, y);
    }

    /**
     * @desc 锁定指定的位置
     * @param posInContent 锁定坐标在scrollView.content下的的节点坐标
     * @param duration 锁定用时
     */
    public scrollToPos(posInContent: cc.Vec3, duration: number = 0) {
        let worldPos = this._contentUITrans.convertToWorldSpaceAR(cc.v3(posInContent.x, posInContent.y));
        let screenCenterWorldPos = this._scrollViewUITrans.convertToWorldSpaceAR(cc.Vec3.ZERO);
        let posOffset = cc.v2(screenCenterWorldPos.x, screenCenterWorldPos.y).subtract(cc.v2(worldPos.x, worldPos.y));
        let curScrollOffset = this.scrollView.getScrollOffset();
        let targetOffset = curScrollOffset.add(posOffset);
        this.scrollView.scrollToOffset(cc.v2(-targetOffset.x, targetOffset.y), duration);
    }

    /**
     * @desc 滚动到指定目标
     * @param itemIdx 目标索引
     * @param duration 滚动时间
     */
    public scrollToItem(itemIdx: number, duration: number = 0) {
        let offset = Math.floor(this._spawnCount / 2);
        const startIdx = Math.min(Math.max(itemIdx - offset, 0), this._itemDataList.length - this._spawnCount)
        for (let i = 0; i < this._items.length; i++) {
            const item = this._items[i];
            const index = startIdx + i
            const data = this._itemDataList[index]

            const posInContent = this._getItemPosByItemId(index);
            item.setPosition(posInContent);
            if (this.itemClassName) {
                const com: ListViewItemBase = (item.getComponent(this.itemClassName) || item.addComponent(this.itemClassName)) as ListViewItemBase;
                com.setDelegage(this.vc)
                com.updateItem(index, data, this._itemDataList.length);
                this.updateItem && this.updateItem(item, data, index);
            }
        }

        const posInContent = this._getItemPosByItemId(itemIdx);
        this.scrollToPos(posInContent, duration);
    }

    /**滚动到指定目标 */
    public scrollToOffsetByItemId(itemIdx: number, offset: number = 0, duration: number = 0.5) {
        let itemPos = itemIdx / this._spawnItemCount;
        this.scrollView.scrollToOffset(cc.v2(0, (this.itemSize.height + this.spacing.y) * itemPos + offset), duration)
    }

    /**
     * @en
     * Scroll the content to the bottom boundary of ScrollView.
     *
     * @zh
     * 视图内容将在规定时间内滚动到视图底部。
     *
     * @param timeInSecond
     * @en The rolling time(in seconds). If time is up, the content will slide to the bottom border. @zh 滚动时间（s）。 如果超时，内容将立即跳到底部边界。
     * @param attenuated @en Whether the rolling acceleration is attenuated(The default is true) @zh 滚动加速是否衰减，默认为 true
     * @example
     * ```ts
     * // Scroll to the bottom of the view.
     * scrollView.scrollToBottom(0.1);
     * ```
     */
    public scrollToBottom(timeInSecond?: number, attenuated?: boolean) {
        this.scrollView && this.scrollView.scrollToBottom(timeInSecond, attenuated);
    }
    /**
     * @en
     * Scroll the content to the top boundary of ScrollView.
     *
     * @zh
     * 视图内容将在规定时间内滚动到视图顶部。
     *
     * @param timeInSecond
     * @en The rolling time(in seconds). If time is up, the content will slide to the bottom border. @zh 滚动时间（s）。 如果超时，内容将立即跳到底部边界。
     * @param attenuated @en Whether the rolling acceleration is attenuated(The default is true) @zh 滚动加速是否衰减，默认为 true
     * @example
     * ```ts
     * // Scroll to the top of the view.
     * scrollView.scrollToTop(0.1);
     * ```
     */
    public scrollToTop(timeInSecond?: number, attenuated?: boolean) {
        this.scrollView && this.scrollView.scrollToTop(timeInSecond, attenuated);
    }
    /**
     * @en
     * Scroll the content to the left boundary of ScrollView.
     *
     * @zh
     * 视图内容将在规定时间内滚动到视图左边。
     *
     * @param timeInSecond
     * @en The rolling time(in seconds). If time is up, the content will slide to the bottom border. @zh 滚动时间（s）。 如果超时，内容将立即跳到底部边界。
     * @param attenuated @en Whether the rolling acceleration is attenuated(The default is true) @zh 滚动加速是否衰减，默认为 true
     * @example
     * ```ts
     * // Scroll to the left of the view.
     * scrollView.scrollToLeft(0.1);
     * ```
     */
    public scrollToLeft(timeInSecond?: number, attenuated?: boolean) {
        this.scrollView && this.scrollView.scrollToLeft(timeInSecond, attenuated);
    }

    /**
     * @en
     * Scroll the content to the right boundary of ScrollView.
     *
     * @zh
     * 视图内容将在规定时间内滚动到视图右边。
     *
     * @param timeInSecond
     * @en The rolling time(in seconds). If time is up, the content will slide to the bottom border. @zh 滚动时间（s）。 如果超时，内容将立即跳到底部边界。
     * @param attenuated @en Whether the rolling acceleration is attenuated(The default is true) @zh 滚动加速是否衰减，默认为 true
     * @example
     * ```ts
     * // Scroll to the right of the view.
     * scrollView.scrollToRight(0.1);
     * ```
     */
    public scrollToRight(timeInSecond?: number, attenuated?: boolean) {
        this.scrollView && this.scrollView.scrollToRight(timeInSecond, attenuated);
    }
    /**销毁 */
    public onDestroy() {
    }
}